package org.naxx.security.naxxlet.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;

import org.apache.log4j.Logger;
import org.naxx.security.common.Decision;
import org.naxx.security.model.match.MatchParentType;
import org.naxx.security.naxxlet.NaxxClassLoader;
import org.naxx.security.naxxlet.NaxxletLoader;
import org.naxx.security.naxxlet.model.XAbstractCondition;
import org.naxx.security.naxxlet.model.XAbstractMatchContainer;
import org.naxx.security.naxxlet.model.XPolicy;
import org.naxx.security.naxxlet.model.XRule;
import org.naxx.security.naxxlet.model.XTarget;
import org.naxx.security.util.Assert;
import org.naxx.security.util.FileSystemUtil;
import org.naxx.security.util.Util;

@SuppressWarnings("unused")
public final class NaxxletLoaderImpl implements NaxxletLoader {
	
	private static final Logger logger = Logger.getLogger(NaxxletLoaderImpl.class);
	
	//root directory of all naxxlets
	private String naxxletsDirectory;
	private NaxxClassLoader classLoader;
	
	public void setNaxxletsDirectory(String naxxletsDirectory) {
		this.naxxletsDirectory = naxxletsDirectory;
		this.classLoader = new NaxxClassLoader(naxxletsDirectory);
	}

	/**
	 * Represents name of the folder.
	 */
	@Override
	public XPolicy loadNaxxlet(String naxxletName) throws Exception {
		File naxxlet = new File(this.naxxletsDirectory + File.separatorChar + naxxletName 
				+ File.separatorChar + "naxxlet.properties");
		Assert.assertFileExistsAndReadable(naxxlet);
		InputStream in = null;
		try {
			in = new FileInputStream(naxxlet);
			Properties naxxletManifest = new Properties();
			naxxletManifest.load(in);
			return this.load(naxxletManifest);
		} finally {
			FileSystemUtil.close(in);
		}
	}

	private XPolicy load(Properties props) throws Exception {
		String policyId = props.getProperty("policy.id");
		int numberOfRules = Integer.valueOf(props.getProperty("number.of.rules"));
		XPolicy policy = new XPolicy(policyId);
		for (int i = 0; i < numberOfRules; i++) {
			XRule rule = new XRule();
			String prefix = "rule[" + i + "].";
			String ruleId = props.getProperty(prefix + "id");
			
			boolean hasCondition = Boolean.valueOf(props.getProperty(prefix + "has.condition"));
			if(hasCondition) {
				String conditionBinaryClassName = props.getProperty(prefix + "condition.class");
				XAbstractCondition condition = this.createConditionInstance(conditionBinaryClassName);
				rule.setCondition(condition);
			}
			
			Decision decision = Decision.valueOf(props.getProperty(prefix + "effect"));
			rule.setEffect(decision);
			
			Boolean hasTarget = Boolean.valueOf(props.getProperty(prefix + "has.target"));
			if(hasTarget) {
				XTarget target = new XTarget();
				String matchClasses = props.getProperty("match.container.classes");
				String[] classes = Util.trim(matchClasses.split(","));
				for (int j = 0; j < classes.length; j++) {
					XAbstractMatchContainer matchContainer = this.createMatchContainerInstance(classes[i]);
					this.addMatchContainer(target, matchContainer);
				}
			}
		}
		return policy;
	}
	
	@SuppressWarnings("unchecked")
	private XAbstractMatchContainer createMatchContainerInstance(String className) throws Exception {
		logger.info("Loading condition class '" + className + "'");
		Class clazz = Class.forName(className, true, this.classLoader); 
		if(!XAbstractMatchContainer.class.isAssignableFrom(clazz)) {
			throw new IllegalArgumentException("Class '" + className + "' is not subclass of '" 
					+ XAbstractMatchContainer.class.getCanonicalName() + "'");
		}
		return (XAbstractMatchContainer) clazz.newInstance();
	}
	
	@SuppressWarnings("unchecked")
	private XAbstractCondition createConditionInstance(String binaryClassName) throws Exception {
		logger.info("Loading condition class '" + binaryClassName + "'");
		Class clazz = Class.forName(binaryClassName, true, this.classLoader);
		if(!XAbstractCondition.class.isAssignableFrom(clazz)) {
			throw new IllegalArgumentException("Class '" + binaryClassName + "' is not subclass of '" 
					+ XAbstractCondition.class.getCanonicalName() + "'");
		}
		return (XAbstractCondition) clazz.newInstance();
	}
	
	private void addMatchContainer(XTarget target, XAbstractMatchContainer matchContainer) {
		if(MatchParentType.ACTION.equals(matchContainer.getMatchParentType())) {
			target.addActionMatchContainer(matchContainer);
		} 
		else if(MatchParentType.ENVIRONMENT.equals(matchContainer.getMatchParentType())) {
			target.addEnvironmentMatchContainer(matchContainer);
		}
		else if(MatchParentType.RESOURCE.equals(matchContainer.getMatchParentType())) {
			target.addResourceMatchContainer(matchContainer);
		}
		else if(MatchParentType.SUBJECT.equals(matchContainer.getMatchParentType())) {
			target.addSubjectMatchContainer(matchContainer);
		}
		else {
			throw new IllegalArgumentException("Unknown ParentMatchType '" 
					+ matchContainer.getMatchParentType() + "'");
		}
	}
}
